/*
 * Copyright (c) 2017, AEye
 * All rights reserved.
 */

#ifndef AEYE_SENSOR_HPP
#define AEYE_SENSOR_HPP

#include <cstddef>
#include <cstdint>
#include <fstream>
#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>

#include "NonBlockTCPSocket.h"
#include "AEyeSensorAPI.h"

namespace aeye {

/**
 * \class SensorCommander
 * \brief Provides the command interface with an AEye sensor.
 */
class SensorCommander {
public:
    SensorCommander() = delete;
    /**
     * \brief Constructs an instance of sensor.
     * \param addr The IP address of the sensor.
     * \param cmd_port The command TCP port number.
     */
    explicit SensorCommander(const std::string &addr, uint16_t cmd_port);

    /**
     * @brief disconnect the socket
     */
    void disconnectFromSensor();

    /**
     * \brief Trying to open TCP/IP socket if it has not been opened yet and establish connection to it.
     */
    void connectToSensor();

    /**
     * @brief are we connected ?
     */
    bool isConnected();

    /**
     * \brief Send command to the sensor to begin capturing.
     * \param client_addr The IP address to stream sensor data to.
     * \param range_port The UDP port number to stream the range data to.
     * \param image_port The UDP port number to stream the image data to.
     */
    void cmdStart(const std::string& client_addr, uint16_t range_port);

    /**
     * \brief Send command to the sensor to stop capturing.
     */
    void cmdStop();

    void cmdReboot();

    void writeScannerRegister(uint32_t addr, uint32_t value);
    uint32_t readScannerRegister(uint32_t addr);
    void writeReceiverRegister(uint32_t addr, uint32_t value);
    uint32_t readReceiverRegister(uint32_t addr);
    void setLaserEnable(uint8_t enable);
    void setLaserPower(float value);
    void setRangeDelay(float value);
    void setTime();
    std::string getShotlistName(uint16_t);
    void setNtpServer(bool en, const std::string& ip);
    void setPtpServer(bool en);
    void setGptpServer(bool en);
    void setNetworkConfig(const std::string& server_ip, const std::string& netmask,
                          const std::string& gateway, uint16_t cmdport);
    void getNetworkConfig(std::string& server_ip, std::string& netmask,
                          std::string& gateway, uint16_t& cmdport);

    PacketGetNtpStatus getNtpStatus();
    PacketGetPtpStatus getPtpStatus();
    PacketGetGptpStatus getGptpStatus();

    aeye::PacketGetPoseBody getPoseFromSensor();
    aeye::PacketGetPassiveSensingStateBody getPassiveSensingState();
    aeye::PacketGetLaserStateBodyReturnV2 getLaserState();
    aeye::PacketGetRangingStateBodyReturn getRangingState();

    void setPose(double x, double y, double z, double yaw, double pitch, double roll);

    void setPassiveSensingMode(uint8_t passiveModeMask);
    void setPassiveModeParameter(PassiveModeParam parameter, float value);
    void setPointReturnMask(uint16_t pointReturnMask);
    void setIntensityType(uint8_t intensityType);

    void setUseOfJumboPackets(bool value);

    void setSendMarkerPoints(bool value);

    void getGenerateARxLutV2Status(bool &isRunning,
                                   uint8_t &calIterations,
                                   uint8_t &startScore,
                                   uint64_t &endScore,
                                   float &ratio);

  /**
  * @brief set the number of multi echo returns
  */
  void sendNumMultiEchoes(uint8_t numEchoes);
  PacketGetNumMultiEchoes getNumMultiEchoes();

  /**
  * @brief sets multi echo return mode
  */
  void setModeMultiEchoes(MultiEchoMode mode);
  PacketGetModeMultiEchoes getModeMultiEchoes();

  PacketBuildInfoExtraBody getBuildInfoExtra();


    /**
     * \brief Requesting build info from sensor
     * @return string with build info from sensor
     */
    std::pair<std::string, PacketBuildInfoBody> cmdGetBuildInfo();
    ModelType cmdGetSensorModel();

    void setSkirtThreshNearHigh(uint16_t value);
    void setSkirtThreshNearLow(uint16_t value);
    void setSkirtThreshFarHigh(uint16_t value);
    void setSkirtThreshFarLow(uint16_t value);
    uint16_t getSkirtThreshNearHigh();
    uint16_t getSkirtThreshNearLow();
    uint16_t getSkirtThreshFarHigh();
    uint16_t getSkirtThreshFarLow();

    // hawkeye functions
    void toggleHawkEye(bool enable, const std::string& clientIPAddress, uint16_t port);
    void setHawkEyePoint(bool enable, uint16_t row, uint16_t col);
    void toggleARxSweepMode(bool enable, const std::string &clientIPAddress, uint16_t port, GainChannel gain_channel, PixelMode pixelMode, uint16_t colSize, uint16_t rowSize);
    void toggleARxStaticMode(bool enable, const std::string& clientIPAddress, uint16_t port, PixelMode pixelMode, uint32_t row, uint32_t col);
    void generateARxLUTV2(RxFOV rxFov, PixelMode, uint8_t rxWindowSize);
    void setRxFOV(RxFOV rx_fov);

      /**
    * @brief Returns Hawkeye buffer on the Hawkeye Port.
    * @param bufferCount: Specify the number of frames to return
    */
    void getHawkeyeBuffer(uint32_t bufferCount=1);

    /*
    * Enables changing ARx pixel dynamically from the client
    * @param col: Rx col (0-127)
    * @param row: Rx row (0-63)
    * @param pixelMode: Rx pixel mode (0=1x1; 1=2x2; 2=3x3)
    */
    void setARxPixel(uint8_t col, uint8_t row, uint8_t pixelMode);

    /*
    * Enables Hawkeye in scanning mode
    */
    void toggleScanningMode(bool enable);

    /*
     * Sends a set of horizontal zone scan paramaters to the sensor, from which it creates a Range Point List to scan.
     */
    void sendScanZoneParameters(std::vector<AdvancedZoneScanRegion>&);

    void killSensorService();

  void setHFOV(float percentage);

  PacketGetHsyncParams getHsyncParameters();

  void SetTCPKeepAlive(bool enable);

  bool GetTCPKeepAlive();

 private:
    std::string addr_; //!< IP Address of the device.
    uint16_t cmd_port_; //!< TCP port on device for statuses and commands.
    NonBlockTCPSocket socket_;

    std::unordered_map<AEyeCommands, uint8_t> const commandVersionsMap;

    void checkBodyType(const std::string& functionName, const PacketHeader& returnHeader,
                       aeye::AEyeCommands packetType);
    void checkBodySize(const std::string& functionName, const std::string& typeName, const PacketHeader& returnHeader,
                       size_t size);
    template <typename T>
    void checkBodyTypeAndSize(const std::string& functionName, const aeye::PacketHeader& returnHeader,
                              aeye::AEyeCommands packetType, const T& body);

    void ReceivePacketBody(void *body, size_t size);

    PacketHeader fillHeader(AEyeCommands packetType);

    void sendCommand(aeye::AEyeCommands packet_type); //!< Overloaded send function. No header version.
    aeye::PacketReturnDefault sendCommandReceiveDefault(aeye::AEyeCommands packetType);

    void sendCommand(const aeye::PacketHeader& header, const void* data, size_t size); //!< Overloaded send function. Header version.
    aeye::PacketReturnDefault sendCommandReceiveDefault(const aeye::PacketHeader& header, const void* data, size_t size);

    template <typename T> //!< Template for packet types.
    void sendCommand(aeye::AEyeCommands packetType, const T* packet); //!< Overloaded send function.  Templated for multiple packet types.
    template <typename T> //!< Template for packet types.
    aeye::PacketReturnDefault  sendCommandReceiveDefault(aeye::AEyeCommands packetType, const T* packet);

    aeye::PacketReturnDefault readDefaultResponse();
    aeye::PacketHeader readResponseHeader();
    aeye::PacketBuildInfoBody readBuildInfoResponse(std::string &infoStr);
    aeye::PacketHWModelInfoBody readGetHWModelTypeResponse();

    aeye::PacketGetPoseBody readPoseResponse();
    aeye::PacketGetPassiveSensingStateBody readPassiveSensingStateResponse();
    aeye::PacketGetERPsStateBody readERPsStateResponse();
    PacketGetLaserStateBodyReturnV2 readLaserStateResponse();
    PacketGetRangingStateBodyReturn readRangingStateResponse();

    PacketReturnShotlistNameBody readGetShotListNameResponse(std::string&);
};

}

#endif // AEYE_SENSOR_HPP
